// This file is part of the RECODER library and protected by the LGPL.

package recodercs.csharp.statement;

import recodercs.*;
import recodercs.csharp.*;
import recodercs.csharp.expression.*;
import recodercs.list.*;

/**
 Loop statement.
 @author <TT>AutoDoc</TT>
 */

public abstract class LoopStatement extends CSharpStatement implements StatementContainer, ExpressionContainer {

    /**
     Guard.
     */

    protected Expression guard;

    /**
     Inits.
     */

    protected LoopInitializerMutableList inits;

    /**
     Updates.
     */

    protected ExpressionMutableList updates;

    /**
     Body.
     */

    protected Statement body;

    /**
     Loop statement.
     */

    public LoopStatement() {}

    /**
     Loop statement.
     @param body a statement.
     */

    public LoopStatement(Statement body) {
        setBody(body);
    }

    /**
     Loop statement.
     @param proto a loop statement.
     */

    protected LoopStatement(LoopStatement proto) {
        super(proto);
        if (proto.guard != null) {
            guard = (Expression)proto.guard.deepClone();
        }
        if (proto.inits != null) {
            inits = (LoopInitializerMutableList)proto.inits.deepClone();
        }
        if (proto.updates != null) {
            updates = (ExpressionMutableList)proto.updates.deepClone();
        }
        if (proto.body != null) {
            body = (Statement)proto.body.deepClone();
        }
    }

    /**
     Make parent role valid.
     */

    public void makeParentRoleValid() {
        super.makeParentRoleValid();
        if (inits != null) {
            for (int i = inits.size() - 1; i >= 0; i -= 1) {
                LoopInitializer li = inits.getLoopInitializer(i);
                if (li instanceof ExpressionStatement) {
                    ((ExpressionStatement)li).setExpressionContainer(this);
                } else {
		    // LocalVariableDeclaration
                    li.setStatementContainer(this);
                }
            }
        }
        if (guard != null) {
            guard.setExpressionContainer(this);
        }
        if (updates != null) {
            for (int i = updates.size() - 1; i >= 0; i -= 1) {
                updates.getExpression(i).setExpressionContainer(this);
            }
        }
        if (body != null) {
            body.setStatementContainer(this);
        }
    }

    /**
     Returns the number of children of this node.
     @return an int giving the number of children of this node
    */

    public int getChildCount() {
        int result = 0;
        if (inits   != null) result += inits.size();
        if (guard   != null) result++;
        if (updates != null) result += updates.size();
        if (body    != null) result++;
        return result;
    }

    /**
     Returns the child at the specified index in this node's "virtual"
     child array
     @param index an index into this node's "virtual" child array
     @return the program element at the given position
     @exception ArrayIndexOutOfBoundsException if <tt>index</tt> is out
                of bounds
    */

    public ProgramElement getChildAt(int index) {
        int len;
        if (inits != null) {
            len = inits.size();
            if (len > index) {
                return inits.getProgramElement(index);
            }
            index -= len;
        }
        if (isCheckedBeforeIteration()) {
            if (guard != null) {
                if (index == 0) return guard;
                index--;
            }
        }
        if (updates != null) {
            len = updates.size();
            if (len > index) {
                return updates.getProgramElement(index);
            }
            index -= len;
        }
        if (body != null) {
            if (index == 0) return body;
            index--;
        }
        if (!isCheckedBeforeIteration()) {
            if (guard != null) {
                if (index == 0) return guard;
                index--;
            }
        }
        throw new ArrayIndexOutOfBoundsException();
    }


    public int getChildPositionCode(ProgramElement child) {
        // role 0 (IDX): init (For only)
        // role 1: guard
        // role 2 (IDX): update (For only)
        // role 3: body
        if (inits != null) {
            int index = inits.indexOf(child);
            if (index >= 0) {
                return (index << 4) | 0;
            }
        }
        if (guard == child) {
            return 1;
        }
        if (updates != null) {
            int index = updates.indexOf(child);
            if (index >= 0) {
                return (index << 4) | 2;
            }
        }
        if (body == child) {
            return 3;
        }
        return -1;
    }


    /**
     * Replace a single child in the current node.
     * The child to replace is matched by identity and hence must be known
     * exactly. The replacement element can be null - in that case, the child
     * is effectively removed.
     * The parent role of the new child is validated, while the
     * parent link of the replaced child is left untouched.
     * @param p the old child.
     * @param p the new child.
     * @return true if a replacement has occured, false otherwise.
     * @exception ClassCastException if the new child cannot take over
     *            the role of the old one.
     */

    public boolean replaceChild(ProgramElement p, ProgramElement q) {
        int count;
        if (p == null) {
            throw new NullPointerException();
        }
        count = (inits == null) ? 0 : inits.size();
        for (int i = 0; i < count; i++) {
            if (inits.getProgramElement(i) == p) {
                if (q == null) {
                    inits.remove(i);
                } else {
                    LoopInitializer r = (LoopInitializer)q;
                    inits.set(i, r);
                    if (r instanceof ExpressionStatement) {
                        ((ExpressionStatement)r).setExpressionContainer(this);
                    } else {
                        r.setStatementContainer(this);
                    }
                }
                return true;
            }
        }
        if (guard == p) {
            Expression r = (Expression)q;
            guard = r;
            if (r != null) {
                r.setExpressionContainer(this);
            }
            return true;
        }
        count = (updates == null) ? 0 : updates.size();
        for (int i = 0; i < count; i++) {
            if (updates.getProgramElement(i) == p) {
                if (q == null) {
                    updates.remove(i);
                } else {
                    Expression r = (Expression)q;
                    updates.set(i, r);
                    r.setExpressionContainer(this);
                }
                return true;
            }
        }
        if (body == p) {
            Statement r = (Statement)q;
            body = r;
            if (r != null) {
                r.setStatementContainer(this);
            }
            return true;
        }
        return false;
    }


    /**
     Get the number of expressions in this container.
     @return the number of expressions.
     */

    public int getExpressionCount() {
        int result = 0;
        if (guard != null) result += 1;
        if (inits != null) {
            for (int i = inits.size() - 1; i >= 0; i -= 1) {
                if (inits.getLoopInitializer(i) instanceof Expression) {
                    result += 1;
                }
            }
        }
        if (updates != null) {
            result += updates.size();
        }
        return result;
    }

    /*
      Return the expression at the specified index in this node's
      "virtual" expression array.
      @param index an index for an expression.
      @return the expression with the given index.
      @exception ArrayIndexOutOfBoundsException if <tt>index</tt> is out
      of bounds.
    */

    public Expression getExpressionAt(int index) {
        if (guard != null) {
            if (index == 0) {
                return guard;
            }
            index -= 1;
        }
        if (inits != null) {
            int s = inits.size();
            for (int i = 0; i < s && index >= 0; i++) {
                LoopInitializer ii = inits.getLoopInitializer(i);
                if (ii instanceof Expression) {
                    if (index == 0) {
                        return (Expression)ii;
                    }
                    index -= 1;
                }
            }
        }
        if (updates != null) {
            return updates.getExpression(index);
        }
        throw new ArrayIndexOutOfBoundsException();
    }

    /**
     Get guard.
     @return the expression.
     */

    public Expression getGuard() {
        return guard;
    }

    /**
     Set guard.
     @param expr an expression.
     */

    public void setGuard(Expression expr) {
        guard = expr;
    }

    /**
     Get body.
     @return the statement.
     */

    public Statement getBody() {
        return body;
    }

    /**
     Set body.
     @param statement a statement.
     */

    public void setBody(Statement statement) {
        body = statement;
    }

    /**
     Get the number of statements in this container.
     @return the number of statements.
     */

    public int getStatementCount() {
        return (body != null) ? 1 : 0;
    }

    /*
      Return the statement at the specified index in this node's
      "virtual" statement array.
      @param index an index for a statement.
      @return the statement with the given index.
      @exception ArrayIndexOutOfBoundsException if <tt>index</tt> is out
      of bounds.
    */

    public Statement getStatementAt(int index) {
        if (body != null && index == 0) {
            return body;
        }
        throw new ArrayIndexOutOfBoundsException();
    }

    /**
     Get initializers.
     @return the loop initializer mutable list.
     */

    public LoopInitializerMutableList getInitializers() {
        return inits;
    }

    /**
     Get updates.
     @return the expression mutable list.
     */

    public ExpressionMutableList getUpdates() {
        return updates;
    }

    /**
     Is exit condition.
     @return the boolean value.
     */

    public abstract boolean isExitCondition();

    /**
     Is checked before iteration.
     @return the boolean value.
     */

    public abstract boolean isCheckedBeforeIteration();
}
